import { assert } from "cc";
import { InjectableObject } from "../../inject";
import { IMediator } from "../base/IMediator";
import { IMediatorMap } from "../base/IMediatorMap";
import { View_Name_Tag } from "./ViewMap";

export type MediatorConstructor<T = IMediator> = new (...args: any[]) => T;

class MappingConfig {
    mediatorClassName: string;
    typedViewClasses: Array<string>;
    autoCreate: boolean;
    autoRemove: boolean;
}

export class MediatorMap extends InjectableObject implements IMediatorMap {

    protected mediatorByView = new Map();

    protected mappingConfigByView = new Map();

    protected mappingConfigByViewName = new Map();

    private viewListenerCount: number = 0;

    mapView(viewName: string, mediatorClassName: string, autoCreator: any = true, autoRemove: boolean = true): void {

        let config: MappingConfig = new MappingConfig;

        config.mediatorClassName = mediatorClassName;
        config.autoCreate = autoCreator;
        config.autoRemove = autoRemove;

        this.mappingConfigByViewName.set(viewName, config);

        if (autoCreator || autoRemove) {
            this.viewListenerCount++;
            if (this.viewListenerCount === 1)
                this.addListeners();
        }

        // if (autoCreator) {
        //     this.createMediatorUsing(viewName, config);
        // }
    }

    unmapView(viewName: string): void {

        let config: MappingConfig = this.mappingConfigByViewName.get(viewName);
        if (config && (config.autoCreate || config.autoRemove)) {
            this.viewListenerCount--;

            if (this.viewListenerCount === 0)
                this.removeListeners();
        }

        this.mappingConfigByViewName.delete(viewName);
    }

    createMediator(viewComponent: any): IMediator {
        return this.createMediatorUsing(viewComponent);
    }

    registerMediator(viewComponent: any, mediator: IMediator): void {

        if (this.mediatorByView.get(viewComponent)) {
            return;
        }

        let viewName = this.getViewName(viewComponent);

        assert(viewName, 'the View must be in ViewMap');

        this.injector.hasMapping(viewName) && this.injector.unmap(viewName);
        this.injector.mapValue(viewName, mediator);

        this.mediatorByView.set(viewComponent, mediator);
        this.mappingConfigByView.set(viewComponent, this.mappingConfigByViewName.get(viewName));

        mediator.setView(viewComponent);

        mediator.preRegister();
    }

    removeMediator(mediator: IMediator): IMediator {
        if (mediator) {
            let viewComponent = mediator.getView();
            let viewName = this.getViewName(viewComponent);

            this.mediatorByView.delete(mediator.getView());
            this.mappingConfigByView.delete(mediator.getView());

            mediator.preRemove();
            mediator.setView(null);

            this.injector.hasMapping(viewName) && this.injector.unmap(viewName);
        }
        return mediator;
    }

    removeMediatorByView(viewComponent: any): IMediator {
        return this.removeMediator(this.retrieveMediator(viewComponent));
    }

    retrieveMediator(viewComponent: any): IMediator {
        return this.mediatorByView.get(viewComponent);
    }

    hasMapping(viewName: string): boolean {
        return this.mappingConfigByViewName.get(viewName) != undefined;
    }

    hasMediator(mediator: IMediator): boolean {

        for (let [key, value] of this.mediatorByView) {
            if (value === mediator)
                return true;
        }

        return false;
    }

    hasMediatorForView(viewComponent: any): boolean {
        return this.mediatorByView.get(viewComponent) != undefined;
    }

    /////////////////  Internal

    protected addListeners(): void {
        this.dispatcher.on('Evt_Added_To_Stage', this.onViewAdded, this);
        this.dispatcher.on('Evt_Removed_From_State', this.onViewRemoved, this);
    }

    protected removeListeners(): void {
        this.dispatcher.targetOff(this);
    }

    protected onViewAdded(viewComponent): void {
        let viewName = this.getViewName(viewComponent);
        assert(viewName, 'the View must be in ViewMap');

        let config: MappingConfig = this.mappingConfigByViewName.get(viewName);
        if (config && config.autoCreate) {
            this.createMediatorUsing(viewComponent, viewName, config);
        }
    }

    protected onViewRemoved(viewComponent): void {
        let config: MappingConfig = this.mappingConfigByView.get(viewComponent);
        if (config && config.autoRemove) {
            this.removeMediatorByView(viewComponent);
        }
    }

    createMediatorUsing(viewComponent: any, viewName?: string, config?: MappingConfig): IMediator {
        let mediator: IMediator = this.mediatorByView.get(viewComponent);
        if (!mediator) {
            viewName = viewName ? viewName : this.getViewName(viewComponent);
            config = config ? config : this.mappingConfigByViewName.get(viewName);

            if (config) {
                mediator = this.injector.instantiateOf(config.mediatorClassName);

                this.registerMediator(viewComponent, mediator);
            }
        }

        return mediator;
    }

    getViewName(viewComponent) {
        return viewComponent[View_Name_Tag];
    }

}